/*
 * Copyright (c) 2008, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.invoke;

import java.lang.reflect.Array;
import java.util.Arrays;

import static java.lang.invoke.MethodHandleStatics.*;
import static java.lang.invoke.MethodHandleNatives.Constants.*;
import static java.lang.invoke.MethodHandles.Lookup.IMPL_LOOKUP;
import static java.lang.invoke.LambdaForm.*;

/**
 * Construction and caching of often-used invokers.
 *
 * @author jrose
 */
class Invokers {

  // exact type (sans leading taget MH) for the outgoing call
  private final MethodType targetType;

  // Cached adapter information:
  private final @Stable
  MethodHandle[] invokers = new MethodHandle[INV_LIMIT];
  // Indexes into invokers:
  static final int
      INV_EXACT = 0,  // MethodHandles.exactInvoker
      INV_GENERIC = 1,  // MethodHandles.invoker (generic invocation)
      INV_BASIC = 2,  // MethodHandles.basicInvoker
      INV_LIMIT = 3;

  /**
   * Compute and cache information common to all collecting adapters
   * that implement members of the erasure-family of the given erased type.
   */
    /*non-public*/ Invokers(MethodType targetType) {
    this.targetType = targetType;
  }

  /*non-public*/ MethodHandle exactInvoker() {
    MethodHandle invoker = cachedInvoker(INV_EXACT);
    if (invoker != null) {
      return invoker;
    }
    invoker = makeExactOrGeneralInvoker(true);
    return setCachedInvoker(INV_EXACT, invoker);
  }

  /*non-public*/ MethodHandle genericInvoker() {
    MethodHandle invoker = cachedInvoker(INV_GENERIC);
    if (invoker != null) {
      return invoker;
    }
    invoker = makeExactOrGeneralInvoker(false);
    return setCachedInvoker(INV_GENERIC, invoker);
  }

  /*non-public*/ MethodHandle basicInvoker() {
    MethodHandle invoker = cachedInvoker(INV_BASIC);
    if (invoker != null) {
      return invoker;
    }
    MethodType basicType = targetType.basicType();
    if (basicType != targetType) {
      // double cache; not used significantly
      return setCachedInvoker(INV_BASIC, basicType.invokers().basicInvoker());
    }
    invoker = basicType.form().cachedMethodHandle(MethodTypeForm.MH_BASIC_INV);
    if (invoker == null) {
      MemberName method = invokeBasicMethod(basicType);
      invoker = DirectMethodHandle.make(method);
      assert (checkInvoker(invoker));
      invoker = basicType.form().setCachedMethodHandle(MethodTypeForm.MH_BASIC_INV, invoker);
    }
    return setCachedInvoker(INV_BASIC, invoker);
  }

  private MethodHandle cachedInvoker(int idx) {
    return invokers[idx];
  }

  private synchronized MethodHandle setCachedInvoker(int idx, final MethodHandle invoker) {
    // Simulate a CAS, to avoid racy duplication of results.
    MethodHandle prev = invokers[idx];
    if (prev != null) {
      return prev;
    }
    return invokers[idx] = invoker;
  }

  private MethodHandle makeExactOrGeneralInvoker(boolean isExact) {
    MethodType mtype = targetType;
    MethodType invokerType = mtype.invokerType();
    int which = (isExact ? MethodTypeForm.LF_EX_INVOKER : MethodTypeForm.LF_GEN_INVOKER);
    LambdaForm lform = invokeHandleForm(mtype, false, which);
    MethodHandle invoker = BoundMethodHandle.bindSingle(invokerType, lform, mtype);
    String whichName = (isExact ? "invokeExact" : "invoke");
    invoker = invoker
        .withInternalMemberName(MemberName.makeMethodHandleInvoke(whichName, mtype), false);
    assert (checkInvoker(invoker));
    maybeCompileToBytecode(invoker);
    return invoker;
  }

  /**
   * If the target type seems to be common enough, eagerly compile the invoker to bytecodes.
   */
  private void maybeCompileToBytecode(MethodHandle invoker) {
    final int EAGER_COMPILE_ARITY_LIMIT = 10;
    if (targetType == targetType.erase() &&
        targetType.parameterCount() < EAGER_COMPILE_ARITY_LIMIT) {
      invoker.form.compileToBytecode();
    }
  }

  // This next one is called from LambdaForm.NamedFunction.<init>.
    /*non-public*/
  static MemberName invokeBasicMethod(MethodType basicType) {
    assert (basicType == basicType.basicType());
    try {
      //Lookup.findVirtual(MethodHandle.class, name, type);
      return IMPL_LOOKUP
          .resolveOrFail(REF_invokeVirtual, MethodHandle.class, "invokeBasic", basicType);
    } catch (ReflectiveOperationException ex) {
      throw newInternalError("JVM cannot find invoker for " + basicType, ex);
    }
  }

  private boolean checkInvoker(MethodHandle invoker) {
    assert (targetType.invokerType().equals(invoker.type()))
        : java.util.Arrays.asList(targetType, targetType.invokerType(), invoker);
    assert (invoker.internalMemberName() == null ||
        invoker.internalMemberName().getMethodType().equals(targetType));
    assert (!invoker.isVarargsCollector());
    return true;
  }

  /**
   * Find or create an invoker which passes unchanged a given number of arguments and spreads the
   * rest from a trailing array argument. The invoker target type is the post-spread type {@code
   * (TYPEOF(uarg*), TYPEOF(sarg*))=>RT}. All the {@code sarg}s must have a common type {@code C}.
   * (If there are none, {@code Object} is assumed.}
   *
   * @param leadingArgCount the number of unchanged (non-spread) arguments
   * @return {@code invoker.invokeExact(mh, uarg*, C[]{sarg*}) := (RT)mh.invoke(uarg*, sarg*)}
   */
    /*non-public*/ MethodHandle spreadInvoker(int leadingArgCount) {
    int spreadArgCount = targetType.parameterCount() - leadingArgCount;
    MethodType postSpreadType = targetType;
    Class<?> argArrayType = impliedRestargType(postSpreadType, leadingArgCount);
    if (postSpreadType.parameterSlotCount() <= MethodType.MAX_MH_INVOKER_ARITY) {
      return genericInvoker().asSpreader(argArrayType, spreadArgCount);
    }
    // Cannot build a generic invoker here of type ginvoker.invoke(mh, a*[254]).
    // Instead, factor sinvoker.invoke(mh, a) into ainvoker.invoke(filter(mh), a)
    // where filter(mh) == mh.asSpreader(Object[], spreadArgCount)
    MethodType preSpreadType = postSpreadType
        .replaceParameterTypes(leadingArgCount, postSpreadType.parameterCount(), argArrayType);
    MethodHandle arrayInvoker = MethodHandles.invoker(preSpreadType);
    MethodHandle makeSpreader = MethodHandles
        .insertArguments(Lazy.MH_asSpreader, 1, argArrayType, spreadArgCount);
    return MethodHandles.filterArgument(arrayInvoker, 0, makeSpreader);
  }

  private static Class<?> impliedRestargType(MethodType restargType, int fromPos) {
    if (restargType.isGeneric()) {
      return Object[].class;  // can be nothing else
    }
    int maxPos = restargType.parameterCount();
    if (fromPos >= maxPos) {
      return Object[].class;  // reasonable default
    }
    Class<?> argType = restargType.parameterType(fromPos);
    for (int i = fromPos + 1; i < maxPos; i++) {
      if (argType != restargType.parameterType(i)) {
        throw newIllegalArgumentException("need homogeneous rest arguments", restargType);
      }
    }
    if (argType == Object.class) {
      return Object[].class;
    }
    return Array.newInstance(argType, 0).getClass();
  }

  public String toString() {
    return "Invokers" + targetType;
  }

  static MemberName methodHandleInvokeLinkerMethod(String name,
      MethodType mtype,
      Object[] appendixResult) {
    int which;
    switch (name) {
      case "invokeExact":
        which = MethodTypeForm.LF_EX_LINKER;
        break;
      case "invoke":
        which = MethodTypeForm.LF_GEN_LINKER;
        break;
      default:
        throw new InternalError("not invoker: " + name);
    }
    LambdaForm lform;
    if (mtype.parameterSlotCount() <= MethodType.MAX_MH_ARITY - MH_LINKER_ARG_APPENDED) {
      lform = invokeHandleForm(mtype, false, which);
      appendixResult[0] = mtype;
    } else {
      lform = invokeHandleForm(mtype, true, which);
    }
    return lform.vmentry;
  }

  // argument count to account for trailing "appendix value" (typically the mtype)
  private static final int MH_LINKER_ARG_APPENDED = 1;

  /**
   * Returns an adapter for invokeExact or generic invoke, as a MH or constant pool linker.
   * If !customized, caller is responsible for supplying, during adapter execution,
   * a copy of the exact mtype.  This is because the adapter might be generalized to
   * a basic type.
   *
   * @param mtype the caller's method type (either basic or full-custom)
   * @param customized whether to use a trailing appendix argument (to carry the mtype)
   * @param which bit-encoded 0x01 whether it is a CP adapter ("linker") or MHs.invoker value
   * ("invoker"); 0x02 whether it is for invokeExact or generic invoke
   */
  private static LambdaForm invokeHandleForm(MethodType mtype, boolean customized, int which) {
    boolean isCached;
    if (!customized) {
      mtype = mtype.basicType();  // normalize Z to I, String to Object, etc.
      isCached = true;
    } else {
      isCached = false;  // maybe cache if mtype == mtype.basicType()
    }
    boolean isLinker, isGeneric;
    String debugName;
    switch (which) {
      case MethodTypeForm.LF_EX_LINKER:
        isLinker = true;
        isGeneric = false;
        debugName = "invokeExact_MT";
        break;
      case MethodTypeForm.LF_EX_INVOKER:
        isLinker = false;
        isGeneric = false;
        debugName = "exactInvoker";
        break;
      case MethodTypeForm.LF_GEN_LINKER:
        isLinker = true;
        isGeneric = true;
        debugName = "invoke_MT";
        break;
      case MethodTypeForm.LF_GEN_INVOKER:
        isLinker = false;
        isGeneric = true;
        debugName = "invoker";
        break;
      default:
        throw new InternalError();
    }
    LambdaForm lform;
    if (isCached) {
      lform = mtype.form().cachedLambdaForm(which);
      if (lform != null) {
        return lform;
      }
    }
    // exactInvokerForm (Object,Object)Object
    //   link with java.lang.invoke.MethodHandle.invokeBasic(MethodHandle,Object,Object)Object/invokeSpecial
    final int THIS_MH = 0;
    final int CALL_MH = THIS_MH + (isLinker ? 0 : 1);
    final int ARG_BASE = CALL_MH + 1;
    final int OUTARG_LIMIT = ARG_BASE + mtype.parameterCount();
    final int INARG_LIMIT = OUTARG_LIMIT + (isLinker && !customized ? 1 : 0);
    int nameCursor = OUTARG_LIMIT;
    final int MTYPE_ARG = customized ? -1 : nameCursor++;  // might be last in-argument
    final int CHECK_TYPE = nameCursor++;
    final int CHECK_CUSTOM = (CUSTOMIZE_THRESHOLD >= 0) ? nameCursor++ : -1;
    final int LINKER_CALL = nameCursor++;
    MethodType invokerFormType = mtype.invokerType();
    if (isLinker) {
      if (!customized) {
        invokerFormType = invokerFormType.appendParameterTypes(MemberName.class);
      }
    } else {
      invokerFormType = invokerFormType.invokerType();
    }
    Name[] names = arguments(nameCursor - INARG_LIMIT, invokerFormType);
    assert (names.length == nameCursor)
        : Arrays.asList(mtype, customized, which, nameCursor, names.length);
    if (MTYPE_ARG >= INARG_LIMIT) {
      assert (names[MTYPE_ARG] == null);
      BoundMethodHandle.SpeciesData speciesData = BoundMethodHandle.speciesData_L();
      names[THIS_MH] = names[THIS_MH].withConstraint(speciesData);
      NamedFunction getter = speciesData.getterFunction(0);
      names[MTYPE_ARG] = new Name(getter, names[THIS_MH]);
      // else if isLinker, then MTYPE is passed in from the caller (e.g., the JVM)
    }

    // Make the final call.  If isGeneric, then prepend the result of type checking.
    MethodType outCallType = mtype.basicType();
    Object[] outArgs = Arrays.copyOfRange(names, CALL_MH, OUTARG_LIMIT, Object[].class);
    Object mtypeArg = (customized ? mtype : names[MTYPE_ARG]);
    if (!isGeneric) {
      names[CHECK_TYPE] = new Name(NF_checkExactType, names[CALL_MH], mtypeArg);
      // mh.invokeExact(a*):R => checkExactType(mh, TYPEOF(a*:R)); mh.invokeBasic(a*)
    } else {
      names[CHECK_TYPE] = new Name(NF_checkGenericType, names[CALL_MH], mtypeArg);
      // mh.invokeGeneric(a*):R => checkGenericType(mh, TYPEOF(a*:R)).invokeBasic(a*)
      outArgs[0] = names[CHECK_TYPE];
    }
    if (CHECK_CUSTOM != -1) {
      names[CHECK_CUSTOM] = new Name(NF_checkCustomized, outArgs[0]);
    }
    names[LINKER_CALL] = new Name(outCallType, outArgs);
    lform = new LambdaForm(debugName, INARG_LIMIT, names);
    if (isLinker) {
      lform.compileToBytecode();  // JVM needs a real methodOop
    }
    if (isCached) {
      lform = mtype.form().setCachedLambdaForm(which, lform);
    }
    return lform;
  }

  /*non-public*/
  static WrongMethodTypeException newWrongMethodTypeException(MethodType actual,
      MethodType expected) {
    // FIXME: merge with JVM logic for throwing WMTE
    return new WrongMethodTypeException("expected " + expected + " but found " + actual);
  }

  /**
   * Static definition of MethodHandle.invokeExact checking code.
   */
    /*non-public*/
  static
  @ForceInline
  void checkExactType(Object mhObj, Object expectedObj) {
    MethodHandle mh = (MethodHandle) mhObj;
    MethodType expected = (MethodType) expectedObj;
    MethodType actual = mh.type();
    if (actual != expected) {
      throw newWrongMethodTypeException(expected, actual);
    }
  }

  /**
   * Static definition of MethodHandle.invokeGeneric checking code.
   * Directly returns the type-adjusted MH to invoke, as follows:
   * {@code (R)MH.invoke(a*) => MH.asType(TYPEOF(a*:R)).invokeBasic(a*)}
   */
    /*non-public*/
  static
  @ForceInline
  Object checkGenericType(Object mhObj, Object expectedObj) {
    MethodHandle mh = (MethodHandle) mhObj;
    MethodType expected = (MethodType) expectedObj;
    return mh.asType(expected);
        /* Maybe add more paths here.  Possible optimizations:
         * for (R)MH.invoke(a*),
         * let MT0 = TYPEOF(a*:R), MT1 = MH.type
         *
         * if MT0==MT1 or MT1 can be safely called by MT0
         *  => MH.invokeBasic(a*)
         * if MT1 can be safely called by MT0[R := Object]
         *  => MH.invokeBasic(a*) & checkcast(R)
         * if MT1 can be safely called by MT0[* := Object]
         *  => checkcast(A)* & MH.invokeBasic(a*) & checkcast(R)
         * if a big adapter BA can be pulled out of (MT0,MT1)
         *  => BA.invokeBasic(MT0,MH,a*)
         * if a local adapter LA can cached on static CS0 = new GICS(MT0)
         *  => CS0.LA.invokeBasic(MH,a*)
         * else
         *  => MH.asType(MT0).invokeBasic(A*)
         */
  }

  static MemberName linkToCallSiteMethod(MethodType mtype) {
    LambdaForm lform = callSiteForm(mtype, false);
    return lform.vmentry;
  }

  static MemberName linkToTargetMethod(MethodType mtype) {
    LambdaForm lform = callSiteForm(mtype, true);
    return lform.vmentry;
  }

  // skipCallSite is true if we are optimizing a ConstantCallSite
  private static LambdaForm callSiteForm(MethodType mtype, boolean skipCallSite) {
    mtype = mtype.basicType();  // normalize Z to I, String to Object, etc.
    final int which = (skipCallSite ? MethodTypeForm.LF_MH_LINKER : MethodTypeForm.LF_CS_LINKER);
    LambdaForm lform = mtype.form().cachedLambdaForm(which);
    if (lform != null) {
      return lform;
    }
    // exactInvokerForm (Object,Object)Object
    //   link with java.lang.invoke.MethodHandle.invokeBasic(MethodHandle,Object,Object)Object/invokeSpecial
    final int ARG_BASE = 0;
    final int OUTARG_LIMIT = ARG_BASE + mtype.parameterCount();
    final int INARG_LIMIT = OUTARG_LIMIT + 1;
    int nameCursor = OUTARG_LIMIT;
    final int APPENDIX_ARG = nameCursor++;  // the last in-argument
    final int CSITE_ARG = skipCallSite ? -1 : APPENDIX_ARG;
    final int CALL_MH = skipCallSite ? APPENDIX_ARG : nameCursor++;  // result of getTarget
    final int LINKER_CALL = nameCursor++;
    MethodType invokerFormType = mtype
        .appendParameterTypes(skipCallSite ? MethodHandle.class : CallSite.class);
    Name[] names = arguments(nameCursor - INARG_LIMIT, invokerFormType);
    assert (names.length == nameCursor);
    assert (names[APPENDIX_ARG] != null);
    if (!skipCallSite) {
      names[CALL_MH] = new Name(NF_getCallSiteTarget, names[CSITE_ARG]);
    }
    // (site.)invokedynamic(a*):R => mh = site.getTarget(); mh.invokeBasic(a*)
    final int PREPEND_MH = 0, PREPEND_COUNT = 1;
    Object[] outArgs = Arrays
        .copyOfRange(names, ARG_BASE, OUTARG_LIMIT + PREPEND_COUNT, Object[].class);
    // prepend MH argument:
    System.arraycopy(outArgs, 0, outArgs, PREPEND_COUNT, outArgs.length - PREPEND_COUNT);
    outArgs[PREPEND_MH] = names[CALL_MH];
    names[LINKER_CALL] = new Name(mtype, outArgs);
    lform = new LambdaForm((skipCallSite ? "linkToTargetMethod" : "linkToCallSite"), INARG_LIMIT,
        names);
    lform.compileToBytecode();  // JVM needs a real methodOop
    lform = mtype.form().setCachedLambdaForm(which, lform);
    return lform;
  }

  /**
   * Static definition of MethodHandle.invokeGeneric checking code.
   */
    /*non-public*/
  static
  @ForceInline
  Object getCallSiteTarget(Object site) {
    return ((CallSite) site).getTarget();
  }

  /*non-public*/
  static
  @ForceInline
  void checkCustomized(Object o) {
    MethodHandle mh = (MethodHandle) o;
    if (mh.form.customized == null) {
      maybeCustomize(mh);
    }
  }

  /*non-public*/
  static
  @DontInline
  void maybeCustomize(MethodHandle mh) {
    byte count = mh.customizationCount;
    if (count >= CUSTOMIZE_THRESHOLD) {
      mh.customize();
    } else {
      mh.customizationCount = (byte) (count + 1);
    }
  }

  // Local constant functions:
  private static final NamedFunction
      NF_checkExactType,
      NF_checkGenericType,
      NF_getCallSiteTarget,
      NF_checkCustomized;

  static {
    try {
      NamedFunction nfs[] = {
          NF_checkExactType = new NamedFunction(Invokers.class
              .getDeclaredMethod("checkExactType", Object.class, Object.class)),
          NF_checkGenericType = new NamedFunction(Invokers.class
              .getDeclaredMethod("checkGenericType", Object.class, Object.class)),
          NF_getCallSiteTarget = new NamedFunction(Invokers.class
              .getDeclaredMethod("getCallSiteTarget", Object.class)),
          NF_checkCustomized = new NamedFunction(Invokers.class
              .getDeclaredMethod("checkCustomized", Object.class))
      };
      for (NamedFunction nf : nfs) {
        // Each nf must be statically invocable or we get tied up in our bootstraps.
        assert (InvokerBytecodeGenerator.isStaticallyInvocable(nf.member)) : nf;
        nf.resolve();
      }
    } catch (ReflectiveOperationException ex) {
      throw newInternalError(ex);
    }
  }

  private static class Lazy {

    private static final MethodHandle MH_asSpreader;

    static {
      try {
        MH_asSpreader = IMPL_LOOKUP.findVirtual(MethodHandle.class, "asSpreader",
            MethodType.methodType(MethodHandle.class, Class.class, int.class));
      } catch (ReflectiveOperationException ex) {
        throw newInternalError(ex);
      }
    }
  }
}
